package zdream.pmfield.test.stage.effect;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveAdditionalEffects;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.data.foe.MoveType;
import zdream.pmfield.data.foe.Type;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.action.AttackReport;
import zdream.pmfield.stage.action.BuffAction;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.AttackMessage;
import zdream.pmfield.stage.message.FoeLeaveMessage;
import zdream.pmfield.stage.message.RollReportMessage;
import zdream.pmfield.stage.message.FoeAddBuffMessage;
import zdream.pmfield.stage.message.FoeReceiveDamageMessage;
import zdream.pmfield.stage.message.FoeRemoveBuffMessage;
import zdream.pmfield.stage.message.StageFinishedMessage;
import zdream.pmfield.stage.message.TurnFinishedMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-11-25
 * @since 0.0.1
 */
public class BurnBuffCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testBurnEffect() {
		MoveTemplate burnMove = storage.findMoveById(10000013, 1);
		assertEquals("肯定烧伤", burnMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(burnMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.build();
		assertTrue(foe2.bStats[BStat.HP.ordinal()] > 30);

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		System.out.println("TEST 1: apply burn");
		AtomicBoolean burnRequest = new AtomicBoolean(false);
		AtomicBoolean burnApplied = new AtomicBoolean(false);
		AtomicBoolean checked = new AtomicBoolean(false);
		float[] buffAppliedTime = new float[1];

		wrapper.printBeforeHandleMessage();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withActionChecker(action -> {
			if (action.move.move == burnMove) {
				burnRequest.set(true);
			}
		}));
		wrapper.onMessage(FoeAddBuffMessage.class, message -> {
			assertTrue(message.buff.is(MoveAdditionalEffects.E_BURN));
			assertTrue(message.success);
			burnApplied.set(true);
			buffAppliedTime[0] = wrapper.now();
		});
		wrapper.onMessage(TurnStartMessage.class, message -> {
			if (!burnRequest.get()) {
				return;
			}
			assertTrue(burnApplied.get());
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());

		System.out.println("TEST 2: burn 状态: 周期性掉血");

		wrapper.clearOnMessageHooks();
		checked.set(false);

		// burn 触发都是在行动过后, 所以先监听到对方行动
		AtomicInteger targetActionCount = new AtomicInteger(0);
		AtomicInteger buffActionCount = new AtomicInteger(0);
		// 预期对方行动进行结束, 下一次它行动前 (targetActionCount == 1) 烧伤会触发

		wrapper.onDefaultActionRequest();
		wrapper.onMessage(ActionStartMessage.class, message -> {
			if (message.action.isForAttack()) {
				final AttackAction attackAction = message.action.asAttack();
				if (attackAction.foe.foe != foe2) {
					return;
				}
				assertEquals(0, targetActionCount.get());
				targetActionCount.incrementAndGet();
			} else if (message.action.isForBuff()) {
				final BuffAction buffAction = message.action.asBuff();
				if (!buffAction.buff.is(StageConstant.BUFF_BURN)) {
					return;
				}
				assertEquals(0, buffActionCount.get());
				buffActionCount.incrementAndGet();
			}
		});
		wrapper.onMessage(FoeReceiveDamageMessage.class, message -> {
			if (message.foe.foe != foe2) {
				return;
			}
			assertEquals(1, targetActionCount.get());
			assertEquals(1, buffActionCount.get());

			assertTrue(message.damage >= 3);
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());

		System.out.println("TEST 3: burn 状态会随时间消失");

		wrapper.clearOnMessageHooks();
		checked.set(false);

		AtomicBoolean burnRemoveCheck = new AtomicBoolean(false);

		wrapper.onDefaultActionRequest();
		wrapper.onMessage(RollReportMessage.class, message -> {
			if (message.foe.foe == foe1) {
				return;
			}
			if (StageConstant.BUFF_BURN.equals(message.title)) {
				burnRemoveCheck.set(true);
				assertTrue(wrapper.now() - buffAppliedTime[0] >= 3);
			}
		});
		wrapper.onMessage(FoeRemoveBuffMessage.class, message -> {
			assertEquals(message.foe.foe, foe2);
			assertTrue(message.buff.is(StageConstant.BUFF_BURN));
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());
	}

	@Test
	public void testFireTypeBlockBurnBuff() {
		MoveTemplate burnMove = storage.findMoveById(10000013, 1);
		assertEquals("肯定烧伤", burnMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000010, 1);
		assertEquals("肯定成功", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(20)
				.withMove(burnMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(37))
				.withLevel(20)
				.withMove(normalMove)
				.build();
		assertEquals(Type.FIRE, foe2.template.types[0]);
		StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		AtomicBoolean checked = new AtomicBoolean(false);

		wrapper.onDefaultActionRequest();
		wrapper.onMessage(FoeAddBuffMessage.class, message -> {
			// 烧伤效果对火系无效
			assertTrue(message.buff.is(StageConstant.BUFF_BURN));
			assertFalse(message.success);
			assertNotNull(message.rejectBy);
			assertTrue(message.rejectBy.is(StageConstant.BUFF_TYPE));
			checked.set(true);
		});
		wrapper.runUntil(s -> checked.get());

		assertTrue(checked.get());
	}

	@Test
	public void testBurnReducePhysicalDamage() {
		MoveTemplate burnMove = storage.findMoveById(10000013, 1);
		assertEquals("肯定烧伤", burnMove.name);
		MoveTemplate physicalMove = storage.findMoveById(10000020, 1);
		assertEquals("物理的", physicalMove.name);
		assertEquals(MoveType.PHYSICAL, physicalMove.attackerUsing);
		MoveTemplate specialMove = storage.findMoveById(10000021, 1);
		assertEquals("特殊的", specialMove.name);
		assertEquals(MoveType.SPECIAL, specialMove.attackerUsing);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(burnMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(physicalMove)
				.withMove(specialMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		System.out.println("TEST 1: apply burn");
		AtomicBoolean burnRequest = new AtomicBoolean(false);
		AtomicBoolean checked = new AtomicBoolean(false);

		wrapper.printBeforeHandleMessage();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withActionChecker(action -> {
			if (action.move.move == burnMove) {
				burnRequest.set(true);
			}
		}));
		wrapper.onMessage(FoeAddBuffMessage.class, message -> {
			assertTrue(message.buff.is(MoveAdditionalEffects.E_BURN));
			assertTrue(message.success);
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());

		System.out.println("TEST 2: test PHYSICAL moves");
		wrapper.clearOnMessageHooks();
		checked.set(false);

		wrapper.onDefaultActionRequest(foe1);
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			if (message.foe.foe != foe2) {
				return;
			}

			final Result result = message.createReplyTemplate()
					.withMove(physicalMove)
					.withTarget(foe1)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.onMessage(AttackMessage.class, message -> {
			final AttackReport report = message.report;
			final AttackAction attackAction = report.action.asAttack();
			if (attackAction.foe.foe != foe2) {
				return;
			}

			assertEquals(1, report.items.size());
			assertEquals(0.75, report.items.get(0).attackFixed, 0.00001);
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());

		System.out.println("TEST 3: test SPECIAL moves");
		wrapper.clearOnMessageHooks();
		checked.set(false);

		wrapper.onDefaultActionRequest(foe1);
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			if (message.foe.foe != foe2) {
				return;
			}

			final Result result = message.createReplyTemplate()
					.withMove(specialMove)
					.withTarget(foe1)
					.commit();

			assertTrue(result.errorMessage, result.success);
		});
		wrapper.onMessage(AttackMessage.class, message -> {
			final AttackReport report = message.report;
			final AttackAction attackAction = report.action.asAttack();
			if (attackAction.foe.foe != foe2) {
				return;
			}

			assertEquals(1, report.items.size());
			assertEquals(1, report.items.get(0).attackFixed, 0);
			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
		assertTrue(checked.get());
	}

	@Test
	public void testBuffActionWhenFoeLeaved() {
		MoveTemplate burnMove = storage.findMoveById(10000013, 1);
		assertEquals("肯定烧伤", burnMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);
		MoveTemplate killMove = storage.findMoveById(10000030, 1);
		assertEquals("一击必杀", killMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(burnMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.withMove(killMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		System.out.println("TEST 1: apply burn");
		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);

		wrapper.clearOnMessageHooks();
		wrapper.onActionRequestForAll(wrapper.createDefaultController().withActionChecker(action -> {
			assertEquals(foe1, action.foe.foe);
			assertEquals(burnMove, action.move.move);
		}));
		wrapper.runUntil(FoeAddBuffMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertTrue(message.buff.is(StageConstant.EFFECT_BURN));
			assertTrue(message.success);
			return true;
		});

		System.out.println("TEST 2: 现在 foe2 要自杀, 预期 burn action 会自动清理掉, 而且会出 TurnFinishedMessage");
		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {

			final Result result = message.createReplyTemplate()
					.withMove(killMove)
					.withSelfTarget()
					.commit();

			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(ActionRequestMessage.class);

		wrapper.runUntil(AttackMessage.class, message -> {
			assertTrue(message.report.action.isForAttack());
			assertEquals(foe2, message.report.action.asAttack().foe.foe);
			assertEquals(1, message.report.items.size());

			final AttackReport.AttackReportItem item = message.report.items.get(0);
			assertEquals(foe2, item.foe.foe);
			assertEquals(0, item.foe.foe.currentHp);
			return true;
		});
		wrapper.runUntil(FoeLeaveMessage.class, message -> {
			assertEquals(foe2, message.foe);
			assertEquals(FoeLeaveMessage.DepartureReason.FAINT, message.reason);
			return true;
		});

		final FoeActor foe2Actor = wrapper.stage.findFoe(foe2);
		assertEquals(1, foe2Actor.buffs.size());
		assertTrue(foe2Actor.buffs.get(0).is(FoeActor.ORIGINAL_BUFF_CATEGORY));

		// 这个时候烧伤的 BuffAction 不应该再触发了
		wrapper.onMessage(ActionStartMessage.class, message -> {
			if (message.action.isForBuff()) {
				assertFalse(message.action.asBuff().buff.is(StageConstant.BUFF_BURN));
			}
		});

		wrapper.runUntil(TurnFinishedMessage.class);
		wrapper.runUntil(StageFinishedMessage.class);
	}
}
